<!--
  Copyright 2016 The LUCI Authors. All rights reserved.
  Use of this source code is governed under the Apache License, Version 2.0
  that can be found in the LICENSE file.
  -->

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/google-signin/google-signin-aware.html">
<link rel="import" href="../bower_components/iron-icons/iron-icons.html">
<link rel="import" href="../bower_components/iron-icons/av-icons.html">
<link rel="import" href="../bower_components/iron-icons/editor-icons.html">
<link rel="import" href="../bower_components/paper-button/paper-button.html">
<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">

<!--
An element for rendering muxed LogDog log streams.
-->
<dom-module id="logdog-stream-view">

  <template>
    <style is="custom-style">
      #mainView {
        position: relative;
      }

      #buttons {
        position: fixed;
        height: auto;
        padding: 5px;
        background-color: rgba(0, 0, 0, 0.1);
        z-index: 100;
      }
      #buttons > paper-button {
        background-color: white;
      }

      .paper-button-highlight[toggles][active] {
        background-color: #cd6a51;
      }

      .paper-icon-button-highlight[toggles][active] {
        background-color: #cd6a51;
        border-radius: 80%;
      }

      #streamStatus {
        position: fixed;
        right: 16px;
        background-color: #EEEEEE;
        opacity: 0.7;
      }

      #logContent {
        padding-top: 54px; /* Pad around buttons */
        background-color: white;
      }

      .log-entry {
        padding: 0 0 0 0;
        clear: left;
      }

      .log-entry-meta {
        background-color: lightgray;
        padding: 5px;
        border-width: 2px 0px 0px 0px;
        border-color: darkgray;
        border-style: dotted;
        user-select: none;

        font-style: italic;
        font-family: Courier New, Courier, monospace;
        font-size: 10px;

        /* Can be toggled to "flex" by applying .showMeta class to #logs. */
        display: none;
      }
      .log-entry-meta-line {
        padding: 5px;
        border-width: 1px;
        border-style: solid;
        border-color: gray;
        border-radius: 10px;
        margin-right: 10px;
        text-align: center;
      }
      .showMeta .log-entry-meta {
        display: flex;
      }

      /* .log-entry-content { */
      .log-entry-chunk {
        padding: 0 0 0 0;
        margin: 0 0 0 0;
        float: none;
        font-family: Courier New, Courier, monospace;
        font-size: 16px;
        list-style: none;

        border-bottom: 1px solid #CCCCCC;
        -webkit-font-smoothing: auto;

        /* Can be toggled by applying .wrapLines class to #logs. */
        white-space: pre;
      }

      /*.wrapLines .log-entry-content { */
      .wrapLines .log-entry-chunk {
        white-space: pre-wrap;
        word-break: break-word;
      }

      .logFetchButtonContainer {
        height: auto;
        display: none;
        flex-direction: row;
        background-color: rgba(0, 0, 0, 0.2);
        padding: 2px;
      }

      .logFetchButtonVisible {
        display: flex !important;
      }

      .logFetchButton {
        width: 100%;
        height: 18px;
      }

      .logSplitUpButton {
        background: yellow;
      }
      .logSplitDownButton {
        background: green;
      }

      .logBottomButton {
        background-color: lightcoral;
      }

      #logEnd {
        margin-bottom: 30px;
        background-color: gray;
      }

      .clickable-log-anchor {
        height: 24px;
      }

      #status-bar {
        /* Overlay at the bottom of the page. */
        position: fixed;
        z-index: 9999;
        overflow: hidden;
        bottom: 0;
        left: 0;
        width: 100%;
        user-select: none;

        text-align: center;
        font-size: 16px;
        background-color: rgba(245, 245, 220, 0.7);
      }
    </style>

    <rpc-client
      id="client"
      auto-token
      host="[[host]]"></rpc-client>

    <!--
      This must be after "rpc-client" so we get the signin event after it
      does.
    -->
    <google-signin-aware
      id="aware"
      on-google-signin-aware-success="_onSignin"></google-signin-aware>

    <!-- Stream view options. -->
    <div id="mainView">
      <div id="buttons">
        <template is="dom-if" if="{{showPlayPause}}">
          <paper-icon-button class="paper-icon-button-highlight" toggles
              title="Auto-Load" icon="{{playingIconName}}"
              active="{{playing}}">
          </paper-icon-button>
        </template>

        <template is="dom-if" if="{{showSplitButton}}">
          <paper-icon-button
              title="Jump to latest."
              icon="editor:vertical-align-bottom"
              on-tap="_splitClicked">
          </paper-icon-button>
        </template>

        <template is="dom-if" if="{{showStreamControls}}">
          <template is="dom-if" if="{{showSplitControls}}">
            <paper-icon-button toggles
                title="Load new logs, or backfill from top."
                icon="{{backfillIconName}}"
                active="{{backfill}}">
            </paper-icon-button>

            <paper-icon-button title="Scroll to split"
                icon="editor:vertical-align-center" on-tap="_scrollToSplit">
            </paper-icon-button>
          </template>

          <paper-button class="paper-button-highlight" toggles raised
              active="{{wrapLines}}">
            Wrap
          </paper-button>

          <template is="dom-if" if="{{metadata}}">
            <paper-button class="paper-button-highlight" toggles raised
                active="{{showMetadata}}">
              Metadata
            </paper-button>
          </template>
        </template>
      </div>

      <!-- Display current fetching status, if stream data is still loading. -->
      <div id="streamStatus">
        <template is="dom-if" if="{{streamStatus}}">
          <table>
            <template is="dom-repeat" items="{{streamStatus}}">
              <tr>
                <td>{{item.name}}</td>
                <td>{{item.desc}}</td>
              </tr>
            </template>
          </table>
        </template>
      </div>

      <!-- Muxed log content. -->
      <div id="logContent">
        <div id="logs">
          <!-- Content will be populated with JavaScript as logs are loaded.

            <div class="log-entry">
              <div class="log-entry-meta">
                <div class="log-entry-meta-line">(Meta 0)</div>
                ...
                <div class="log-entry-meta-line">(Meta N)</div>
              </div>
              <div class="log-entry-content">
                LINE #0
                ...
                LINE #N
              </div>
            </div>
            ...


            Note that we can't use templating to show/hide the log dividers,
            since our positional log insertion requires them to be present and
            move along with insertions as points of reference.
          -->

          <div id="logSplit" class="logFetchButtonContainer">
            <!-- Insert point (prepend for head, append for tail). -->
            <paper-button id="logSplitUp"
                class="logFetchButton logSplitUpButton giant"
                text="Load Above"
                disabled="[[playing]]"
                on-click="_handleUpClick">
              <iron-icon icon="file-upload"></iron-icon>
            </paper-button>
            <paper-button id="logSplitDown"
                class="logFetchButton logSplitDownButton giant"
                text="Load Below"
                disabled="[[playing]]"
                on-click="_handleDownClick">
              <iron-icon icon="file-download"></iron-icon>
            </paper-button>
          </div>

          <div id="logBottom" class="logFetchButtonContainer">
            <!--
              Bottom of the log stream (red bottom line). When tail is complete,
              all future logs get prepended to this.
            -->
            <paper-button id="logBottomButton"
                class="logFetchButton logBottomButton giant"
                disabled="[[playing]]"
                on-click="_handleBottomClick">
              <iron-icon icon="arrow-drop-down"></iron-icon>
            </paper-button>
          </div>
          <div id="logEnd"></div>
        </div>
      </div>

    </div>

    <template is="dom-if" if="{{statusBar}}">
      <div id="status-bar">{{statusBar.value}}</div>
    </template>
  </template>

</dom-module>

<script>
  "use strict";

  Polymer({
    is: "logdog-stream-view",

    properties: {
      /** The name ([host][:port]) of the pRPC host. */
      host: {
        type: String,
        notify: true,
      },

      /**
       * An array of log stream names to load and mux.
       *
       * Each stream entry is a valid stream path prefixed with that stream's
       * project. For example, for stream "foo/bar/+/baz" in project "chromium",
       * the stream path would be: "chromium/foo/bar/+/baz".
       */
      streams: {
        type: Array,
        value: [],
        notify: true,
        observer: "_streamsChanged",
      },

      /**
       * If true, use "mobile" behavior (see model.ts).
       *
       * This will try and load in smaller chunks to consume less data by
       * default.
       */
      mobile: {
        type: Boolean,
        value: false,
      },

      /**
       * The number of logs to load before forcing a page refresh.
       *
       * The smaller the value, the smoother the page will behave while logs are
       * loading. However, the logs will also load slower because of forced
       * renders in between elements.
       */
      burst: {
        type: Number,
        value: 1000,
        notify: true,
      },

      /** If populated, the stream name at the top will link to this URL. */
      streamLinkUrl: {
        type: String,
        value: null,
        notify: true,
      },

      /**
       * If true, render metadata blocks alongside their log entries.
       *
       * This will cause significantly more HTML elements during rendering (so
       * that each metadata element can show up next to its row) and greatly
       * slow the viewer down.
       */
      metadata: {
        type: Boolean,
        value: false,
      },

      /** If true, show log metadata column. */
      showMetadata: {
        type: Boolean,
        value: false,
        observer: "_showMetadataChanged",
      },

      /** If true, wrap log lines to the screen. */
      wrapLines: {
        type: Boolean,
        value: false,
        observer: "_wrapLinesChanged",
      },

      /** Whether or not to show play/pause button. */
      showPlayPause: {
        type: Boolean,
        value: true,
        readOnly: true,
      },

      /** Whether or not to show stream control buttons. */
      showStreamControls: {
        type: Boolean,
        value: false,
        readOnly: true,
      },

      /** Whether or not to show the "split streams" button. */
      showSplitButton: {
        type: Boolean,
        value: false,
        readOnly: true,
      },

      /** Whether or not to show the split controls. */
      showSplitControls: {
        type: Boolean,
        value: false,
        readOnly: true,
      },

      /**
       * True if the viewer should automatically load more logs after the
       * previous batch has finished.
       */
      playing: {
        type: Boolean,
        value: false,
        observer: "_playingChanged",
      },

      /**
       * (Computed) the iron-icon name to use for the playing button.
       */
      playingIconName: {
        type: String,
        computed: '_computePlayingIconName(playing)',
      },

      /**
       * True if the automatic loading direction should backfill from the top
       * (load above the split) instead of continue loading new logs (load below
       * the split).
       */
      backfill: {
        type: Boolean,
        value: false,
        observer: "_backfillChanged",
      },

      /**
       * (Computed) the iron-icon name of the backfill button icon.
       */
      backfillIconName: {
        type: String,
        computed: '_computeBackfillIconName(backfill)',
      },

      /**
       * The current stream status. This is an Array of objects:
       *   obj.name is the name of the stream.
       *   obj.desc is the status description of the stream.
       */
      streamStatus: {
        type: Array,
        value: null,
        notify: true,
        readOnly: true,
      },

      /**
       * The text content of the status element at the bottom of the page.
       */
      statusBar: {
        type: String,
        value: null,
        readOnly: true,
      },
    },

    created: function() {
      this._view = new LogDog.View(this);
    },

    attached: function() {
      this._view.reset();
      this.$.logContent.addEventListener('wheel',
          this._handleMouseWheel.bind(this));
    },

    detached: function() {
      this._view.detach();
      this.$.logContent.removeEventListener('wheel',
          this._handleMouseWheel.bind(this));
    },

    stop: function() {
      this._view.stop();
    },

    reset: function() {
      this._view.reset();
    },

    _polymerAppendChild: function(e) {
      Polymer.dom(this.root).appendChild(e);
    },

    _handleMouseWheel: function(e) {
      this._view.handleMouseWheel(e.deltaY >= 0);
    },

    _handleDownClick: function(e) {
      this._view.handleDownClick();
    },

    _handleUpClick: function(e) {
      this._view.handleUpClick();
    },

    _handleBottomClick: function(e) {
      this._view.handleBottomClick();
    },

    /** Called when the bound log stream variables has changed. */
    _streamsChanged: function() {
      this._view.handleStreamsChanged();
    },

    /**
     * Callback when "showMetadata" has changed. This adds/removes the
     * "showMeta" CSS class from the metadata column.
     */
    _showMetadataChanged: function(v) {
      this.toggleClass("showMeta", v, this.$.logs);
    },

    /**
     * Callback when "wrapLines" has changed. This adds/removes the
     * "wrapLines" CSS class to the log data.
     */
    _wrapLinesChanged: function(v) {
      this.toggleClass("wrapLines", v, this.$.logs);
     },

    /** Callback when "playing" has changed. */
    _playingChanged: function(v) {
      this._view.handlePlayPauseChanged(v);
    },

    _computePlayingIconName: function(playing) {
      return ( (playing) ?
          "av:pause-circle-outline" : "av:play-circle-outline" );
    },

    /** Callback when "backfill" has changed. */
    _backfillChanged: function(v) {
      this._view.handleBackfillChanged(v);
    },

    _computeBackfillIconName: function(backfill) {
      return ( (backfill) ?
          "editor:border-bottom" : "editor:border-top" );
    },

    /** Callback when "split" button has been clicked. */
    _splitClicked: function() {
      this._view.handleSplitClicked();
    },

    /** Callback when "split" button has been clicked. */
    _scrollToSplit: function() {
      this._view.handleScrollToSplitClicked();
    },

    _updateSplitVisible: function(v) {
      this.toggleClass("logFetchButtonVisible", v, this.$.logSplit);
      this.toggleClass("logFetchButtonVisible", v, this.$.logBottom);
    },

    _onSignin: function() {
      this._view.handleSignin();
    },
  });
</script>
